Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
80.00% |
4 / 5 |
CRAP | |
97.73% |
43 / 44 |
| GrantedProductUpdater | |
0.00% |
0 / 1 |
|
80.00% |
4 / 5 |
19 | |
97.73% |
43 / 44 |
| __construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
8 / 8 |
|||
| update | |
0.00% |
0 / 1 |
3.03 | |
85.71% |
6 / 7 |
|||
| checkGrantedFieldsForProductDraft | |
100.00% |
1 / 1 |
6 | |
100.00% |
14 / 14 |
|||
| checkGrantedFieldsForViewableProduct | |
100.00% |
1 / 1 |
7 | |
100.00% |
12 / 12 |
|||
| getUpdatedAssociations | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
| <?php | |
| declare(strict_types=1); | |
| /* | |
| * This file is part of the Akeneo PIM Enterprise Edition. | |
| * | |
| * (c) 2017 Akeneo SAS (http://www.akeneo.com) | |
| * | |
| * For the full copyright and license information, please view the LICENSE | |
| * file that was distributed with this source code. | |
| */ | |
| namespace Akeneo\Pim\Permission\Component\Updater; | |
| use Akeneo\Pim\Enrichment\Component\Product\Comparator\Filter\FilterInterface; | |
| use Akeneo\Pim\Enrichment\Component\Product\Model\ProductInterface; | |
| use Akeneo\Pim\Permission\Component\Attributes; | |
| use Akeneo\Pim\Permission\Component\Exception\ResourceAccessDeniedException; | |
| use Akeneo\Tool\Component\StorageUtils\Exception\InvalidObjectException; | |
| use Akeneo\Tool\Component\StorageUtils\Updater\ObjectUpdaterInterface; | |
| use Doctrine\Common\Util\ClassUtils; | |
| use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; | |
| use Symfony\Component\Security\Core\Exception\InvalidArgumentException; | |
| /** | |
| * Apply permissions when updating the product. | |
| * | |
| * @author Marie Bochu <marie.bochu@akeneo.com> | |
| */ | |
| class GrantedProductUpdater implements ObjectUpdaterInterface | |
| { | |
| /** @var ObjectUpdaterInterface */ | |
| private $productUpdater; | |
| /** @var AuthorizationCheckerInterface */ | |
| private $authorizationChecker; | |
| /** @var FilterInterface */ | |
| private $productFieldFilter; | |
| /** @var FilterInterface */ | |
| private $productAssociationFilter; | |
| /** @var array */ | |
| private $supportedFields; | |
| /** @var FilterInterface */ | |
| private $productFilter; | |
| /** @var array */ | |
| private $supportedAssociations; | |
| /** | |
| * @param ObjectUpdaterInterface $productUpdater | |
| * @param AuthorizationCheckerInterface $authorizationChecker | |
| * @param FilterInterface $productFieldFilter | |
| * @param FilterInterface $productAssociationFilter | |
| * @param FilterInterface $productFilter | |
| * @param array $supportedFields | |
| * @param array $supportedAssociations | |
| */ | |
| public function __construct( | |
| ObjectUpdaterInterface $productUpdater, | |
| AuthorizationCheckerInterface $authorizationChecker, | |
| FilterInterface $productFieldFilter, | |
| FilterInterface $productAssociationFilter, | |
| FilterInterface $productFilter, | |
| array $supportedFields, | |
| array $supportedAssociations | |
| ) { | |
| $this->productUpdater = $productUpdater; | |
| $this->authorizationChecker = $authorizationChecker; | |
| $this->productFieldFilter = $productFieldFilter; | |
| $this->productAssociationFilter = $productAssociationFilter; | |
| $this->productFilter = $productFilter; | |
| $this->supportedFields = $supportedFields; | |
| $this->supportedAssociations = $supportedAssociations; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function update($product, array $data, array $options = []) | |
| { | |
| if (!$product instanceof ProductInterface) { | |
| throw InvalidObjectException::objectExpected(ClassUtils::getClass($product), ProductInterface::class); | |
| } | |
| // TODO: PIM-6564 will be done when we'll publish product model | |
| if (null !== $product->getId()) { | |
| $this->checkGrantedFieldsForProductDraft($product, $data); | |
| $this->checkGrantedFieldsForViewableProduct($product, $data); | |
| } | |
| $this->productUpdater->update($product, $data, $options); | |
| return $this; | |
| } | |
| /** | |
| * If product is a draft (that's means the user is not owner of the product but can edit it), | |
| * product's fields cannot be updated, but we allow their presence in the product to facilitate the update. | |
| * To know if a field has been updated, we call Filters | |
| * whose responsibility is to compare submitted data with data in database and return only updated values. | |
| * If Filters return a non empty array, it means user tries to update a non granted field. | |
| * | |
| * @see \Akeneo\Pim\Enrichment\Component\Product\Comparator\Filter\ProductFilterInterface | |
| * | |
| * @param ProductInterface $product | |
| * @param array $data | |
| * | |
| * @throws InvalidArgumentException | |
| */ | |
| private function checkGrantedFieldsForProductDraft(ProductInterface $product, array $data): void | |
| { | |
| $isOwner = $this->authorizationChecker->isGranted([Attributes::OWN], $product); | |
| $canEdit = $this->authorizationChecker->isGranted([Attributes::EDIT], $product); | |
| if (!$isOwner && $canEdit) { | |
| $fields = array_filter($data, function ($code) { | |
| return in_array($code, $this->supportedFields); | |
| }, ARRAY_FILTER_USE_KEY); | |
| $filteredProductFields = !empty($fields) ? $this->productFieldFilter->filter($product, $fields) : []; | |
| $updatedAssociations = $this->getUpdatedAssociations($product, $data); | |
| $updatedFields = array_keys(array_merge($filteredProductFields, $updatedAssociations)); | |
| if (!empty($updatedFields)) { | |
| $message = count($updatedFields) > 1 ? 'following fields' : 'field'; | |
| throw new InvalidArgumentException(sprintf( | |
| 'You cannot update the %s "%s". You should at least own this product to do it.', | |
| $message, | |
| implode(', ', $updatedFields) | |
| )); | |
| } | |
| } | |
| } | |
| /** | |
| * If user can only view the product, data cannot be updated | |
| * but we allow their presence in the product to facilitate the update (in particularly for import) | |
| * | |
| * @see \Akeneo\Pim\Enrichment\Component\Product\Comparator\Filter\ProductFilterInterface | |
| * | |
| * @param ProductInterface $product | |
| * @param array $data | |
| * | |
| * @throws ResourceAccessDeniedException | |
| */ | |
| private function checkGrantedFieldsForViewableProduct(ProductInterface $product, array $data): void | |
| { | |
| $canView = $this->authorizationChecker->isGranted([Attributes::VIEW], $product); | |
| $canEdit = $this->authorizationChecker->isGranted([Attributes::EDIT], $product); | |
| if ($canView && !$canEdit) { | |
| $fields = array_filter($data, function ($code) { | |
| return in_array($code, $this->supportedFields) || 'values' === $code; | |
| }, ARRAY_FILTER_USE_KEY); | |
| $updatedProduct = !empty($fields) ? $this->productFilter->filter($product, $fields) : []; | |
| $updatedAssociations = $this->getUpdatedAssociations($product, $data); | |
| if (!empty($updatedProduct) || !empty($updatedAssociations)) { | |
| throw new ResourceAccessDeniedException($product, sprintf( | |
| 'Product "%s" cannot be updated. It should be at least in an own category.', | |
| $product->getIdentifier() | |
| )); | |
| } | |
| } | |
| } | |
| /** | |
| * Get associations which have been modified | |
| * | |
| * @param ProductInterface $product | |
| * @param array $data | |
| * | |
| * @return array | |
| */ | |
| private function getUpdatedAssociations(ProductInterface $product, array $data): array | |
| { | |
| $associations = array_filter($data, function ($code) { | |
| return in_array($code, $this->supportedAssociations); | |
| }, ARRAY_FILTER_USE_KEY); | |
| return !empty($associations) ? $this->productAssociationFilter->filter($product, $associations) : []; | |
| } | |
| } |